package com.eshop.eshopcache.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.eshop.eshopcache.model.ProductInfo;
import com.eshop.eshopcache.model.ShopInfo;
import com.eshop.eshopcache.service.CacheService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author: Xukai
 * @description: 缓存service实现类
 * @createDate: 2018/7/25 14:36
 * @modified By:
 */
@Service("cacheService")
public class CacheServiceImpl implements CacheService {

    public static final String CACHE_NAME = "local";

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    @CachePut(value = CACHE_NAME, key = "'product_info:'+#productInfo.getId()")
    public ProductInfo saveProductInfo2LocalCache(ProductInfo productInfo) {
        return productInfo;
    }

    @Override
    @Cacheable(value = CACHE_NAME, key = "'product_info:'+#productId")
    public ProductInfo getProductInfoFromLocalCache(Long productId) {
        return null;
    }

    @Override
    @CachePut(value = CACHE_NAME, key = "'shop_info:'+#shopInfo.getId()")
    public ShopInfo saveShopInfo2LocalCache(ShopInfo shopInfo) {
        return shopInfo;
    }

    @Override
    @Cacheable(value = CACHE_NAME, key = "'shop_info:'+#shopId")
    public ShopInfo getShopInfoFromLocalCache(Long shopId) {
        return null;
    }

    // 对redis的请求服务做资源隔离，防止redis宕机造成整个服务被拖垮
    @HystrixCommand(
            commandKey = "saveProductInfo2RedisCache",
            groupKey = "RedisCacheGroup",
            threadPoolKey = "RedisCacheGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "5"),
                    @HystrixProperty(name = "maxQueueSize", value = "20")
            }
    )
    @Override
    public void saveProductInfo2RedisCache(ProductInfo productInfo) {
        String key = "product_info:" + productInfo.getId();
        redisTemplate.opsForValue().set(key, JSONObject.toJSONString(productInfo));
    }

    @HystrixCommand(
            commandKey = "saveShopInfo2RedisCache",
            groupKey = "RedisCacheGroup",
            threadPoolKey = "RedisCacheGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            }
    )
    @Override
    public void saveShopInfo2RedisCache(ShopInfo shopInfo) {
        String key = "shop_info:" + shopInfo.getId();
        redisTemplate.opsForValue().set(key, JSONObject.toJSONString(shopInfo));
    }

    @HystrixCommand(
            commandKey = "getProductInfoFromRedisCache",
            groupKey = "RedisCacheGroup",
            threadPoolKey = "RedisCacheGroupPool",
            fallbackMethod = "getProductInfoFromRedisCacheFallback",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            }
    )
    @Override
    public ProductInfo getProductInfoFromRedisCache(Long productId) {
        String key = "product_info:" + productId;
        String productInfoJson = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(productInfoJson)) {
            return JSONObject.parseObject(productInfoJson, ProductInfo.class);
        }
        return null;
    }

    public ProductInfo getProductInfoFromRedisCacheFallback(Long productId) {
        // fail silent模式 直接返回null
        return null;
    }

    @HystrixCommand(
            commandKey = "getShopInfoFromRedisCache",
            groupKey = "RedisCacheGroup",
            threadPoolKey = "RedisCacheGroupPool",
            fallbackMethod = "getShopInfoFromRedisCacheFallback",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            }
    )
    @Override
    public ShopInfo getShopInfoFromRedisCache(Long shopId) {
        String key = "shop_info:" + shopId;
        String shopInfoJson = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(shopInfoJson)) {
            return JSONObject.parseObject(shopInfoJson, ShopInfo.class);
        }
        return null;
    }

    public ShopInfo getShopInfoFromRedisCacheFallback(Long shopId) {
        // fail silent模式 直接返回null
        return null;
    }

    // 正常情况下，假设每次商品服务的访问性能大约200ms，1个线程一秒可以执行5次访问，
    // 假设商品服务MySQL的访问限制大约每秒150次，此时能允许每秒30个线程，总共每秒可
    // 以访问150次，但如果是非正常情况下，每秒1000次甚至1w次，此时就需要执行限流，
    // 同时还要设置等待队列，线程池大小设置为30等待队列设置为50，目的是30个线程如果
    // 在1s内很快就处理完，仍然可以让MySQL处理等待队列中的请求，免得造成资源浪费。
    @HystrixCommand(
            commandKey = "getProductInfoFromRemoteService",
            groupKey = "RemoteServiceGroup",
            threadPoolKey = "RemoteServiceGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "10"),
                    @HystrixProperty(name = "maximumSize", value = "30"),
                    @HystrixProperty(name = "maxQueueSize", value = "20"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15")
            },
            fallbackMethod = "getProductInfoFromRemoteServiceFallbackFirst"
    )
    @Override
    public ProductInfo getProductInfoFromRemoteService(Long productId) {
        // 模拟从数据库获得了数据
        String productInfoJSON = "{\"id\": " + productId + ", \"name\": \"iphone7手机\", \"price\": 6660, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 12:01:00\"}";
        ProductInfo productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
        return productInfo;
    }

    /**
     * 级联降级的第一级：向habse请求冷数据
     * 注意：fallback要使用独立的线程池
     * 冷数据，比如一个商品信息一周前的过时的数据，放入大数据的在线存储中，比
     * 如分布式存储hbase。发送请求访问hbase加载冷数据，hbase本身是分布式的，所
     * 以也可以承载高并发的访问，即使大量并发到hbase，如果集群运维够好也可以撑
     * 住，虽然是过期数据，但是至少有数据。如果还是超时，就设计多级降级机制，
     * 先走hbase冷备，然后再走stubbed fallback。
     *
     * @param productId
     * @return
     */
    @HystrixCommand(
            commandKey = "getProductInfoFromRemoteServiceFallbackFirst",
            groupKey = "RemoteServiceFallbackGroup",
            threadPoolKey = "RemoteServiceFallbackGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            },
            threadPoolProperties = {
                    @HystrixProperty(name = "coreSize", value = "5"),
                    @HystrixProperty(name = "maximumSize", value = "20"),
                    @HystrixProperty(name = "maxQueueSize", value = "10"),
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5")
            },
            fallbackMethod = "getProductInfoFromRemoteServiceFallbackSecond"
    )
    public ProductInfo getProductInfoFromRemoteServiceFallbackFirst(Long productId) {
        // 模拟从hbase中取数据
        try {
            // 这里设置的等待时间已经超过了超时阈值，触发降级
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ProductInfo productInfo = new ProductInfo();
        return productInfo;
    }

    /**
     * 级联降级的第二级：stubbed fallback，在一切服务都失效后提供残缺的数据
     *
     * @param productId
     * @return
     */
    public ProductInfo getProductInfoFromRemoteServiceFallbackSecond(Long productId) {
        // 拼接数据
        String productInfoJSON = "{\"id\": " + productId + ", \"name\": \"默认商品\", \"price\": 0, \"pictureList\":\"a.jpg\", \"specification\": \"默认规格\", \"service\": \"默认售后服务\", \"color\": \"红色\", \"size\": \"0\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 00:00:00\"}";
        ProductInfo productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
        return productInfo;
    }

    @HystrixCommand(
            commandKey = "getShopInfoFromRemoteService",
            groupKey = "RemoteServiceGroup",
            threadPoolKey = "RemoteServiceGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            },
            fallbackMethod = "getShopInfoFromRemoteServiceFallbackFirst"
    )
    @Override
    public ShopInfo getShopInfoFromRemoteService(Long shopId) {
        String shopInfoJSON = "{\"id\": " + shopId + ", \"name\": \"小王的手机店\", \"level\": 5, \"goodCommentRate\":0.99, \"modifiedTime\": \"2017-01-01 12:01:00\"}";
        ShopInfo shopInfo = JSONObject.parseObject(shopInfoJSON, ShopInfo.class);
        return shopInfo;
    }

    @HystrixCommand(
            commandKey = "getShopInfoFromRemoteServiceFallbackFirst",
            groupKey = "RemoteServiceFallbackGroup",
            threadPoolKey = "RemoteServiceFallbackGroupPool",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "200"),
            },
            fallbackMethod = "getShopInfoFromRemoteServiceFallbackSecond"
    )
    public ShopInfo getShopInfoFromRemoteServiceFallbackFirst(Long productId) {
        // 模拟从hbase中取数据
        try {
            // 这里设置的等待时间已经超过了超时阈值，触发降级
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ShopInfo shopInfo = new ShopInfo();
        return shopInfo;
    }

    /**
     * stubbed fallback，在一切服务都失效后提供残缺的数据
     *
     * @param shopId
     * @return
     */
    public ShopInfo getShopInfoFromRemoteServiceFallbackSecond(Long shopId) {
        String shopInfoJSON = "{\"id\": " + shopId + ", \"name\": \"默认店铺\", \"level\": 0, \"goodCommentRate\":0, \"modifiedTime\": \"2017-01-01 00:00:00\"}";
        ShopInfo shopInfo = JSONObject.parseObject(shopInfoJSON, ShopInfo.class);
        return shopInfo;
    }
}
